/*
 * Copyright 2011 The Rusted Roof, LLC.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

package org.proteusframework.core.base;

import org.proteusframework.core.api.IInterfaceEmitter;
import org.proteusframework.core.api.model.IVersion;
import org.proteusframework.core.util.Assert;

import java.lang.reflect.Method;

/**
 * Semantic representation of the primary versioned interface defined by a <code>IPlugin</code>. A
 * <code>InterfaceEmitter</code> is used during development to generate a version compatible interface for use
 * by the calling <code>IPlugin</code>. This rudimentary interface code generation is accomplished via the
 * {@link #emit(String, String)} method.
 *
 * @author Tacoma Four
 */
public final class InterfaceEmitter implements IInterfaceEmitter
{
    /**
     * Interface class to emit.
     */
    private final Class interfaceClass;

    /**
     * Version of the interface being emitted.
     */
    private final IVersion version;

    /**
     * Constructs an interface emitter targeting the given version of the specified interface.
     *
     * @param version        Version of the interface specified
     * @param interfaceClass Interface to emit
     */
    public InterfaceEmitter(final IVersion version, final Class interfaceClass)
    {
        Assert.parameterNotNull(interfaceClass, "Parameter 'interfaceClass' must not be null");
        Assert.parameterNotNull(version, "Parameter 'version' must not be null");

        if (!interfaceClass.isInterface())
        {
            throw new IllegalArgumentException("Parameter 'interfaceClass' must be an interface");
        }

        this.version = version;
        this.interfaceClass = interfaceClass;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public IVersion getVersion()
    {
        return version;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String emit(String packageName, String interfaceName)
    {
        Assert.parameterNotNull(packageName, "Parameter 'packageName' must not be null");
        Assert.parameterNotNull(interfaceName, "Parameter 'interfaceName' must not be null");

        StringBuffer buf = new StringBuffer("package ").append(packageName).append(";\n\n")
                .append("//\n// CODE GENERATED - DO NOT MODIFY BY HAND\n//\n\n")
                .append("public interface ").append(interfaceName).append("\n{\n")
                .append(enumerateMethods(true))
                .append("\n}");

        return buf.toString();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String listMethods()
    {
        return enumerateMethods(false);
    }

    /**
     * Enumerates the list of methods contained within the interface class.
     *
     * @param formatFlag true to format the output
     * @return String representation of the interface's methods
     */
    private String enumerateMethods(boolean formatFlag)
    {
        StringBuffer buf = new StringBuffer();

        Method[] methods = interfaceClass.getMethods();

        for (int m = 0; m < methods.length; m++)
        {
            Class returnType = methods[m].getReturnType();
            String methodName = methods[m].getName();
            Class[] params = methods[m].getParameterTypes();
            Class[] exceptions = methods[m].getExceptionTypes();

            //
            // Always hide our setPlatformDelegate from prying eyes, even though the framework asserts unitialization
            // during assignment to guarantee that our delegate isn't re-assigned against our will
            if (methodName.equals("setPlatformDelegate"))
            {
                continue;
            }

            if (formatFlag)
            {
                buf.append("\t");
            }

            buf.append(returnType.getCanonicalName())
                    .append(" ")
                    .append(methodName)
                    .append("(");

            for (int i = 0; i < params.length; i++)
            {
                buf.append(params[i].getCanonicalName())
                        .append(" ")
                        .append((char) (i + 97));


                if ((i + 1) < params.length)
                {
                    buf.append(", ");
                }
            }

            buf.append(") ");

            if (exceptions.length > 0)
            {
                buf.append(" throws ");

                for (int j = 0; j < exceptions.length; j++)
                {
                    buf.append(exceptions[j].getCanonicalName());

                    if ((j + 1) < exceptions.length)
                    {
                        buf.append(", ");
                    }
                }
            }
            if (formatFlag)
            {
                buf.append(";");

                if ((m + 1) < methods.length)
                {
                    buf.append("\n\n");
                }

            }
        }
        return buf.toString();
    }
}
